package DataEncryptionStandard;


import constant.CONS;

public class BitStream {

    private static final byte[] TABLE = new byte[]{
            (byte) 0B10000000, 0B01000000, 0B00100000, 0B00010000,
            0B00001000, 0B00000100, 0B00000010, 0B00000001
    };
    private final byte[] value;
    private final int bitLength;

    public BitStream(int bitLength) throws Exception {
        if (bitLength <= 0) {
            throw new Exception(CONS.INDEX_WRONG.getMsg());
        }
        this.bitLength = bitLength;
        this.value = new byte[bitLength % 8 == 0 ? bitLength / 8 : bitLength / 8 + 1];
    }

    public BitStream(int bitLength, byte[] value) throws Exception {
        if (bitLength > value.length * 8 || bitLength <= 0 || value.length * 8 - bitLength >= 8) {
            throw new Exception(CONS.INDEX_WRONG.getMsg());
        }

        this.bitLength = bitLength;
        this.value = value;
    }

    public int getBitLength() {
        return this.bitLength;
    }

    /**
     * 获取比特流中某个坐标的0/1值
     *
     * @param index 下标
     * @return 返回0或者1
     */
    public int get(int index) {
        int remainingBit = value.length * 8 - bitLength;
        // a用来获取第几个byte，b用来获取byte的第几个bit，都是从1开始
        int a = (index + remainingBit - 1) / 8 + 1, b = (index + remainingBit - 1) % 8 + 1;
        return (value[a - 1] & TABLE[b - 1]) == 0 ? 0 : 1;
    }

    public byte[] getValue() {
        return this.value;
    }

    /**
     * 对比特流的某个位置的比特设值
     *
     * @param index  比特流中的下标，下标从1开始算起。
     * @param bitNum 比特流的值
     * @throws Exception 设值的过程参数异常
     */
    public void set(int index, int bitNum) throws Exception {
        if (index > bitLength || index <= 0) {
            throw new Exception(CONS.INDEX_WRONG.getMsg());
        }
        if (bitNum != 0 && bitNum != 1) {
            throw new Exception(CONS.DATA_WRONG.getMsg());
        }

        int remainingBit = value.length * 8 - bitLength;
        // a用来获取第几个byte，b用来获取byte的第几个bit，都是从1开始
        int a = (index + remainingBit - 1) / 8 + 1, b = (index + remainingBit - 1) % 8 + 1;
        if (bitNum == 1) {
            value[a - 1] = (byte) (value[a - 1] | TABLE[b - 1]);
        } else {
            value[a - 1] = (byte) (value[a - 1] & ~TABLE[b - 1]);
        }
    }


    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof BitStream)) return false;
        return this.toString().equals(obj.toString());
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < value.length; i++) {
            res.append(Integer.toBinaryString(Byte.toUnsignedInt(value[i])));
        }

        while (res.length() < bitLength) {
            res.insert(0, '0');
        }

        while (res.length() > bitLength) {
            res.deleteCharAt(0);
        }

        return res.substring(0);
    }

    private void reLeftShiftOne() {
        int remainingBit = 8 * value.length - bitLength;
        int firstBit = this.get(1);
        for (int i = 1; i <= value.length; i++) {
            value[i - 1] <<= 1;
            try {
                if (i <= value.length - 1) {
                    this.set(i * 8 - remainingBit, this.get(i * 8 - remainingBit + 1));
                } else {
                    this.set(bitLength, firstBit);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void reRightShiftOne() {
        int remainingBit = 8 * value.length - bitLength;
        int lastBit = this.get(bitLength);
        for (int i = value.length; i >= 1; i--) {
            value[i] >>= 1;
            try {
                if (i >= 2) {
                    this.set((i - 1) * 8 - remainingBit + 1, this.get((i - 1) * 8 - remainingBit));
                } else {
                    this.set(1, lastBit);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public void reLeftShift(int bits) {
        while (bits-- >= 1) {
            reLeftShiftOne();
        }
    }

    public void reRightShift(int bits) {
        while (bits-- >= 1) {
            reRightShiftOne();
        }
    }
}
